/* eslint-disable max-len */
/* eslint-disable no-multi-assign */
/* eslint-disable no-restricted-properties */
import Event from 'modules/Event';

import {
    touchstart,
    touchmove,
    touchend,
    touchleave,
    isIos,
    isPhone,
    IOS_SYSTEM_SWIPE_WIDTH,
    TOUCH_DIRECTION
} from './config';
import util from './util';

function distance(x, y, x1, y1) {
    return Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2), 2);
}

function getPosition(e, index = 0) {
    return {
        [`x${index || ''}`]: isPhone ? e.touches[index].clientX : e.clientX,
        [`y${index || ''}`]: isPhone ? e.touches[index].clientY : e.clientY,
    };
}

function getFinger(e) {
    return e.touches ? e.touches.length : 1;
}

function prevent(options, event) {
    if (event.cancelable) {
        options.preventDefault && event.preventDefault();
        options.stopPropagation && event.stopPropagation();
    }
}

/*
 * touch core 提供回调
 * 生命周期
 * onNativeStart
 * onStart
 * onNativeChange
 * onBeforeChange
 * onChange
 * onNativeEnd
 * onENd
 * 可在native系列事件中自己控制event preventDefault stopPropagation
 * 添加去抖动功能，比如说单指移动多少以内不算移动
 */
export default class Touch extends Event {
    $dom = document.body

    constructor($dom, options = {}) {
        super();
        this.$dom = $dom;
        this.options = {
            finger: 1, // 默认为单指
            forceResetTime: 500, // 强制reset时长
            preventDefault: true, // 阻止默认事件
            stopPropagation: false, // 是否冒泡
            litenLeaveEvent: true, // 是否监听leave事件
            iosSystemSwipe: true,
            XYWeight: 1,
            ingoreTouchElement: 'ingoreTouchElement', // 忽略touch事件的元素
            canScrollElement: 'canScrollElement', // 可滚动元素
            listenerOptions: {}, // addEventListener options 处理capture/passive等
            activeClassName: '', // 触摸过程中为$dom添加的class
            effectElements: [], // 受影响的元素，会在其dom上添加activeClassName
            ...options,
        };
        this.init();
        this.addEvents();
    }

    // 初始化
    init = () => {
        this.inited = true;
        this.state = {
            start: { x: 0, y: 0 }, // 滑动开始
            move: { x: 0, y: 0 }, // 滑动中
            moveCache: { x: 0, y: 0 }, // 位移缓存
            change: { x: 0, y: 0 }, // x,y位移变化
            end: { x: 0, y: 0 }, // 滑动结束
            V: { x: 0, y: 0 }, // 位移速度
            scale: 1, // 缩放比例
            scalePer: 1, // 缩放变化
            scaleV: 0, // 缩放速度
            rotate: 0, // 旋转角度
            rotatePer: 0, // 旋转变化
            rotateV: 0, // 旋转速度
            finger: 0, // 触摸指个数
            nativeFingers: 0, // 当前dom上的触摸点个数
            enable: false,
            swipeX: true,
            swipeY: true,
            freeze: false,
            direction: '', // lr, ud
            isMove: false,
        };
    }

    /**
     * 在effectElements切换className
     * @param {boolean} [addClass=true]
     * @memberof Touch
     */
    toggleActiveClass(addClass = true) {
        const { effectElements = [], activeClassName } = this.options;
        if (activeClassName) {
            [
                this.$dom,
                ...effectElements,
            ].forEach(dom => {
                dom instanceof window.HTMLElement
                    && dom.classList.toggle(activeClassName, addClass);
            });
        }
    }

    enable() {
        this._isDisable = false;
    }

    /**
     * 阻止任何触摸滑动默认事件，阻止滑动冒泡事件
     * @memberof Touch
     */
    disable() {
        this._isDisable = true;
    }

    destroy() {
        this.isDestroy = true;
        this.removeEvents();
    }

    isAddEvent = false

    getListenerOptions() {
        return {
            passive: false,
            ...this.options.listenerOptions,
        };
    }

    addEvents() {
        if (this.isDestroy || this.isAddEvent) return;
        this.isAddEvent = true;
        this.$dom.addEventListener(touchstart, this.start, this.getListenerOptions());
        this.$dom.addEventListener(touchmove, this.move, this.getListenerOptions());
        this.$dom.addEventListener(touchend, this.end, this.getListenerOptions());
        !isPhone && this.options.litenLeaveEvent && this.$dom.addEventListener(touchleave, this.end, { passive: false });
    }

    removeEvents() {
        this.$dom.removeEventListener(touchstart, this.start);
        this.$dom.removeEventListener(touchmove, this.move);
        this.$dom.removeEventListener(touchend, this.end);
        !isPhone && this.options.litenLeaveEvent && this.$dom.removeEventListener(touchleave, this.end);
        this.isAddEvent = false;
    }

    start = (event) => {
        const { options, state } = this;
        if (this._isDisable) return;

        prevent(options, event);
        this.trigger('nativeStart', event, state);

        // 0到2 保持稳定
        // 2到1 被销毁
        // 2 -> 3 只有一个有用
        state.nativeFingers = getFinger(event);
        this.trigger('finger:change', state.nativeFingers, state.finger);
        if (
            !state.freeze
            && !state.isMove
        ) {
            if (this.checkIsTouchIgnore(event.target)) return;
            // 某些元素在内部滚动到尽头时，可以触发滚动，并且需要锁定滚动方向

            this.setTouchLimit(event.target);

            // 处理ios系统返回
            if (
                isIos
                && options.iosSystemSwipe
                && options.finger === 1
                && (
                    event.touches[0].clientX < IOS_SYSTEM_SWIPE_WIDTH
                    || event.touches[0].clientX > window.innerWidth - IOS_SYSTEM_SWIPE_WIDTH
                )
            ) {
                return;
            }

            state.finger = getFinger(event);
            state.enable = true;
            state.start = state.move = state.moveCache = {
                ...getPosition(event),
                time: Date.now(),
            };
            this.toggleActiveClass();
            // 如果是双指，记录双指位置
            if (options.finger == 2 && getFinger(event) >= options.finger) {
                state.start = state.move = state.moveCache = {
                    ...getPosition(event, 1),
                    ...state.start,
                };
            }
            this.trigger('start', state, event);
        }
    }

    /**
     * 因为有些元素可能是可以滚动的，所以只能在滚动到边界的时候才能触发touch事件监听
     * @param {*} currentTarget
     * @returns
     * @memberof Touch
     */
    setTouchLimit(currentTarget) {
        const { options, state } = this;
        if (
            options.canScrollElement
        ) {
            const scrollElement = util.parent(
                currentTarget,
                this.$dom,
                (d) => d.classList.contains(options.canScrollElement),
            );
            if (scrollElement) {
                const { clientHeight, scrollHeight, scrollTop } = scrollElement;
                const st = Math.ceil(scrollTop);

                if (scrollHeight != clientHeight) {
                    if (st == 0) {
                        // 顶部
                        state.directionLimit = {
                            direction: TOUCH_DIRECTION.topToBottom,
                            only: 1,
                            scrollElement,
                        };
                    } else if (st >= scrollHeight - clientHeight) {
                        // 底部
                        state.directionLimit = {
                            direction: TOUCH_DIRECTION.topToBottom,
                            only: -1,
                            scrollElement,
                        };
                    } else {
                        state.directionLimit = {
                            direction: TOUCH_DIRECTION.topToBottom,
                            only: 0, // 不限定滚动方向
                            scrollElement,
                        };
                    }
                }
            }
        }
    }

    /**
     * 判断当前元素是否是可以忽略滑动监听
     * @param {*} target
     * @returns
     * @memberof Touch
     */
    checkIsTouchIgnore(currentTarget) {
        let { ingoreTouchElement } = this.options;
        if (!Array.isArray(ingoreTouchElement)) {
            ingoreTouchElement = [ingoreTouchElement];
        }
        return ingoreTouchElement.length
            && util.parent(
                currentTarget,
                this.$dom,
                (d) => (
                    ingoreTouchElement.some(item => d.classList.contains(item))
                    || getComputedStyle(d).overflowX === 'scroll'
                )
            );
    }

    /**
     * 判断滑动方向是否受限
     * @param {String} direction
     * @param {Bumber} change
     * @returns
     * @memberof Touch
     */
    checkTouchLimit(direction, change) {
        const { state } = this;
        const { directionLimit } = state;

        if (directionLimit && directionLimit.direction === direction) {
            if (change * directionLimit.only <= 0) {
                return true;
            }
        }
        return false;
    }

    move = (event) => {
        if (this._isDisable) {
            event.preventDefault();
            event.stopPropagation();
            return;
        }

        const { state, options } = this;
        prevent(options, event);
        // 如果想自己控制preventDefault 可以监听nativeChange事件，处理event
        this.trigger('nativeChange', event);

        const {
            start, moveCache, swipeX, swipeY,
        } = state;

        if (state.touchLimit) {
            return;
        }

        if (state.enable && !state.freeze) {
            let move = {
                ...getPosition(event),
                time: Date.now(),
            };
            const T = (move.time - moveCache.time) * 1000;

            if (
                !state.swipeY
                || (swipeX && (Math.abs(move.x - start.x) * options.XYWeight > Math.abs(move.y - start.y)))
            ) {
                // canScrollElement在可以滚动的区间，不trigger滚动事件，但在touchend后要对state做重置
                if (
                    state.swipeY
                    && this.checkTouchLimit(TOUCH_DIRECTION.leftToRight, move.x - start.x)
                ) {
                    state.touchLimit = true;
                    return;
                }
                state.direction = TOUCH_DIRECTION.leftToRight;
                state.isMove = true;
                state.swipeY = false;
            }

            if (
                !state.swipeX
                || (swipeY && (Math.abs(move.x - start.x) * options.XYWeight <= Math.abs(move.y - start.y)))
            ) {
                if (
                    state.swipeX
                    && this.checkTouchLimit(TOUCH_DIRECTION.topToBottom, move.y - start.y)
                ) {
                    state.touchLimit = true;
                    return;
                }
                state.isMove = true;
                state.swipeX = false;
                state.direction = TOUCH_DIRECTION.topToBottom;
            }

            // 开始滚动前
            !state.isMove && this.trigger('beforeChange', state, event);

            if (options.finger >= 2 && getFinger(event) >= options.finger) {
                move = {
                    ...getPosition(event, 1),
                    ...move,
                };
                state.move = move;
                state.change = {
                    x: move.x - start.x,
                    y: move.y - start.y,
                    x1: move.x1 - start.x1,
                    y1: move.y1 - start.y1,
                };
                // 缩放比例
                state.scale = distance(move.x, move.y, move.x1, move.y1) / distance(start.x, start.y, start.x1, start.y1);
                state.scalePer = distance(move.x, move.y, move.x1, move.y1) / distance(moveCache.x, moveCache.y, moveCache.x1, moveCache.y1);
                state.scaleV = state.scalePer / T;
                // 旋转角度
                state.rotate = Math.atan2(move.y - move.y1, move.x - move.x1) - Math.atan2(start.y - start.y1, start.x - start.x1);
                state.rotatePer = Math.atan2(move.y - move.y1, move.x - move.x1) - Math.atan2(moveCache.y - moveCache.y1, moveCache.x - moveCache.x1);
                state.rotateV = state.rotatePer / T;
                // 计算滑动速度
                state.V = {
                    x: (move.x - moveCache.x) / T,
                    y: (move.y - moveCache.y) / T,
                };
                // state.rotate *= 180 / Math.PI
                this.trigger('change', state.change, state, event);
                state.moveCache = { ...state.move };
            } else {
                state.move = move;
                state.change = {
                    x: move.x - start.x,
                    y: move.y - start.y,
                };

                // 计算滑动速度
                state.V = {
                    x: (move.x - moveCache.x) / T,
                    y: (move.y - moveCache.y) / T,
                };
                // 需要提供最后一次有效滑动方向
                // 以及滑动速度
                // 滑动

                // todo，trigger传递的state，应该是不可变state，如果传递引用的话，会导致一些问题
                this.trigger('change', state.change, state, event);
                state.moveCache = state.move;
            }
        }
    }

    touchend = (event) => {
        const { state } = this;
        let isDone = false;
        let timeout;
        const done = () => {
            if (!isDone) {
                clearTimeout(timeout);
                isDone = true;
                const { scale } = this.state;
                this.init();
                this.state.scale = scale;
                this.toggleActiveClass(false);
            }
        };

        // 避免安卓手机，滑动穿透到body上去
        // 根据经验主义，如果滚动穿透到body，点击元素，就会把滚动移交给当前元素
        if (state.directionLimit && state.directionLimit.scrollElement) {
            state.directionLimit.scrollElement.click();
        }
        // 如果是无效滑动依然会在一定时间后执行done，但是没有在touchstart的时候clear定时器
        // timeout = setTimeout(done, options.forceResetTime) // 可能由于种种原因导致应该执行done，但是没有执行，这里用来重置
        if (state.enable && !state.freeze) {
            state.freeze = true; // 冻结，不响应事件

            this.trigger('end', state.change, done, state, event); // 触发onEnd，等待初始化
            // 如果没有移动就立即终结
            if (!state.direction || state.touchLimit) {
                done();
            }
        }
    }

    end = (event) => {
        if (this._isDisable) return false;

        const { state, options } = this;
        if (state.isMove) {
            // options.preventDefault && event.preventDefault()
            // options.stopPropagation && event.stopPropagation()
        }
        // 触发
        state.nativeFingers = getFinger(event);
        this.trigger('finger:change', state.nativeFingers, state.finger);
        this.trigger('nativeEnd', event);
        // 只有当前手指个数小于预期个数才有触发touch end的必要
        if (state.nativeFingers < options.finger || !isPhone) {
            this.touchend(event);
        }
        return false;
    }
}
